/*****************************************************************************
Copyright 2006 Kervin L. Pierre
    EMAIL: otlkcon@adevsoft.com

This file is part of Otlkcon.

Otlkcon is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

Otlkcon is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Lesser GNU General Public License for more details.

You should have received a copy of the Lesser GNU General Public License
along with Otlkcon; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*******************************************************************************/

#include <stdafx.h>
#include "resource.h"

#include <otlkcon_event.h>
#include <otlkcon_generic_msgstore.h>
#include <O_IXPLogon.h>
#include <O_IXPProvider.h>

#include <map>
#include <queue>
using namespace std;

// Queue of events ready to be processed
queue<otlkcon_event_struct *>  otlkcon_events_queue;

extern HWND  otlkcon_eventHwnd;

extern map< O_ENTRYID *, void *, otlkcon_entryid_less >      otlkcon_event_objects;
extern CRITICAL_SECTION    otlkcon_objects_cs;

HRESULT otlkcon_event_sendmessage( HWND sHwnd, HWND rHwnd,
                                   otlkcon_event_struct *event )
{
    HRESULT result = S_OK;
    COPYDATASTRUCT cpData = {0};

    cpData.cbData = sizeof(otlkcon_event_struct)+event->cbData-1;
    cpData.lpData = event;
	event->eventType = event->eventType;

    OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
        L"Event sending... 0x%X\n", event->data[0] );

    //FIXME: Encrypt data first
	BOOL bSendResult = (BOOL)SendMessage(rHwnd, WM_COPYDATA,
		(WPARAM)sHwnd, (LPARAM)&cpData );
	
	if(!bSendResult) 
	{
		DWORD dwError = ::GetLastError();
		dwError = dwError;
	} // if


    return result;
}

typedef struct process_event_struct_
{
    void *obj;
    void *param;
} process_event_struct;

DWORD WINAPI otlkcon_do_event( LPVOID param );

LRESULT CALLBACK otlkcon_event_wndproc(HWND hwnd, UINT umsg, 
                                            WPARAM wparam, LPARAM lparam)
{
    LRESULT result              = TRUE;
    COPYDATASTRUCT *lpCpData    = NULL;
    otlkcon_event_struct *event = NULL;
    map< O_ENTRYID *, void *, otlkcon_entryid_less >::iterator tmpIter;
    otlkcon_event_struct *tmpEvent;
    ULONG tmpSize;
    HANDLE tmpThreadHandle;

    switch( umsg )
    {
        case WM_COPYDATA:
		{
			lpCpData = (COPYDATASTRUCT *)lparam;

			// FIXME:  Do not process the event. Push it into a
			//         local queue and process on WM_TIMER.  This
			//         should be a reasonable workaround for blocking
			//         issue.

			tmpEvent = (otlkcon_event_struct *)(lpCpData->lpData);
			tmpSize = tmpEvent->cbData;
			event = (otlkcon_event_struct*)calloc(1, tmpSize+sizeof(otlkcon_event_struct));
			memcpy(event,tmpEvent,tmpSize+sizeof(otlkcon_event_struct));
			event->recvTime = time(0);

			OTLKCON_ENTERCRITICALSECTION(&otlkcon_objects_cs, "event_wndproc");
			otlkcon_events_queue.push( event );
			OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
				L"Event processed 0x%X\n", event->data[0] );
			OTLKCON_LEAVECRITICALSECTION(&otlkcon_objects_cs, "event_wndproc");

			return TRUE;
		}
        case WM_TIMER:
             switch (wparam) 
            { 
                case IDT_EVENT_TIMER:
                    if( otlkcon_events_queue.size() < 1 )
                    {
                        return TRUE;
                    }

                    event = otlkcon_events_queue.front();
                                                    
                    // Search for the object
					{
						OTLKCON_ENTERCRITICALSECTION(&otlkcon_objects_cs, "event_wndproc");
						tmpIter = otlkcon_event_objects.find( &(event->entryID) );
						if( tmpIter == otlkcon_event_objects.end() )
						{
							//Object is not in memory

							//FIXME: For now we will assume that this will
							//       never happen.  Any object that can receive
							//       an event will be preloaded.
							OTLKCON_DEBUGBREAK;
							otlkcon_events_queue.pop();
							return 0;
						}
						OTLKCON_LEAVECRITICALSECTION(&otlkcon_objects_cs, "event_wndproc");
					}

                    process_event_struct *tmpEvSt 
                        = (process_event_struct*)calloc(1, sizeof(process_event_struct));
                    tmpEvSt->obj = tmpIter->second;
                    tmpEvSt->param = event;
                    tmpThreadHandle = CreateThread( NULL, 0, 
                                otlkcon_do_event, tmpEvSt, 0, NULL );

                    otlkcon_events_queue.pop();

                    return 0;
            }
            return 0;

        default:
            return DefWindowProc(hwnd, umsg, wparam, lparam);
    }

    return result;
}

DWORD WINAPI otlkcon_do_event( LPVOID param )
{
    DWORD result = S_OK;
    otlkcon_generic_msgstore         *tmpStore;
    O_IMSPMsgStore      *tmpMSPStore;
    O_IXPLogonAccount   *tmpLogonAccnt;
    O_IXPProvider       *tmpXPP;
    O_IContainer        *tmpCon;
    process_event_struct *procEvent = (process_event_struct *)param;
    otlkcon_event_struct *event = (otlkcon_event_struct *)procEvent->param;

    result = MAPIInitialize(NULL);

    switch( event->entryID.primarySupportedInterface )
    {
        case OTLKCON_IMSPMSGSTORE_CLASS:
            tmpMSPStore = (O_IMSPMsgStore *)(procEvent->obj);
            result = tmpMSPStore->ProcessEvent( event );
            break;

        case OTLKCON_IMSGSTORE_CLASS:
            tmpStore = (otlkcon_generic_msgstore *)(procEvent->obj);
            result = tmpStore->ProcessEvent( event );
            break;

        case OTLKCON_IXPLOGONACCOUNT_CLASS:
            tmpLogonAccnt = (O_IXPLogonAccount *)(procEvent->obj);
            result = tmpLogonAccnt->ProcessEvent( event );
            break;

        case OTLKCON_IXPPROVIDER_CLASS:
            tmpXPP = (O_IXPProvider *)(procEvent->obj);
            result = tmpXPP->ProcessEvent( event );
            break;

        case OTLKCON_IMAPIFOLDER_CLASS:
            tmpCon = (O_IContainer *)(procEvent->obj);
            result = tmpCon->ProcessEvent( event );
            break;

        default:
            OTLKCON_DEBUGBREAK;
            break;
    }

    MAPIUninitialize();

    return result;
}

DWORD WINAPI otlkcon_event( LPVOID param )
{
	DWORD result = S_OK;
    WNDCLASS wclass = {0};
    MSG msg;

//    result = MAPIInitialize(NULL);

    wclass.style        = CS_GLOBALCLASS;
    wclass.lpfnWndProc  = otlkcon_event_wndproc;
    wclass.hInstance    = GetModuleHandleW(OTLKCON_MSTORE_DLL_NAME);
    wclass.lpszClassName= OTLKCON_EVENT_WINDOW_CLASSNAME;

	ATOM aClass = RegisterClass(&wclass);

    if( aClass == 0 && ERROR_CLASS_ALREADY_EXISTS != GetLastError())
    {
        OTLKCON_DEBUGBREAK;
    }

    if( (otlkcon_eventHwnd = CreateWindow(OTLKCON_EVENT_WINDOW_CLASSNAME,
                                            NULL, 0, 0, 0, 0, 0, 0, 0,
                                            wclass.hInstance, NULL )) == NULL )
    {
        OTLKCON_DEBUGBREAK;
    }

    UINT_PTR timerRes = SetTimer( otlkcon_eventHwnd,IDT_EVENT_TIMER,
                OTLKCON_EVENT_TIMER_RES,(TIMERPROC)NULL );

    while(GetMessage(&msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    try
    {
        // FIXME: Why is this error being thrown?
        UnregisterClass(OTLKCON_EVENT_WINDOW_CLASSNAME, 
                    GetModuleHandleW(OTLKCON_MSTORE_DLL_NAME));
    }
    catch( void * )
    {
        OTLKCON_DEBUGBREAK;
    }

//	MAPIUninitialize();

//event_end:
	return result;
}
